﻿using CloudStorageLight.Core;
using DeltaCompressionDotNet.MsDelta;
using DropNet;
using Ionic.Zip;
using Microsoft.InformationProtectionAndControl;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace CloudStorageLight.Core.Web
{
    public abstract class SettingsControllerBase : Controller
    {
        protected virtual BlobUser GetCurrentUser()
        {
            if (user == null) user = new BlobUser(System.Web.HttpContext.Current.User);
            return user;
        }
        private BlobUser user;


        protected abstract BlobService CreateBlobService();

        public ActionResult DeleteCloudAccount()
        {
            BlobUser user = GetCurrentUser();
            if (!user.IsAdmin) throw new BlobException(BlobExceptionCode.NotAuthorization);
            var svc = CreateBlobService();

            var account = BlobService.GetCurrentCloudAccount();
            if (account == null) throw new BlobException(BlobExceptionCode.NotAuthorization);
            if (!string.Equals(account.EMailAddress, user.Name, StringComparison.InvariantCultureIgnoreCase)) throw new BlobException(BlobExceptionCode.NotAuthorization);
            if (!SystemSettings.Instance.MultiDomain) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var vm = new CloudAccountVM();
            vm.EMailAddress = user.Name;
            vm.CloudAccount = account.AccountName;
            return View(vm);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteCloudAccount(CloudAccountVM vm)
        {
            BlobUser user = GetCurrentUser();
            if (!user.IsAdmin) throw new BlobException(BlobExceptionCode.NotAuthorization);
            var account = BlobService.GetCurrentCloudAccount();
            if (account == null) throw new BlobException(BlobExceptionCode.NotAuthorization);
            if (!string.Equals(account.EMailAddress, user.Name, StringComparison.InvariantCultureIgnoreCase)) throw new BlobException(BlobExceptionCode.NotAuthorization);
            if (!SystemSettings.Instance.MultiDomain) throw new BlobException(BlobExceptionCode.NotAuthorization);

            if (account.AccountName != vm.CloudAccount) throw new BlobException(BlobExceptionCode.NotAuthorization);

            using (var svc = CreateBlobService())
            {
                svc.Destory(user);
            }
            CloudAccount.Delete(SystemSettings.Instance.BlobStorage, account.AccountName);
            return RedirectToAction("CreateCloudAccount", "Account");
        }


        public ActionResult Configuration()
        {
            var user = GetCurrentUser();
            if (!user.IsAdmin) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var data = BlobService.GetCurrentBlobContainerConfig();
            var vm = new BlobConfigurationVM(data);
            return View(vm);
        }

        public async Task<ActionResult> ConfigurationOneDriveCallback(string code)
        {
            BlobService.SetCurrentDomain(Request.Cookies["OneDriveDomain"].Value);
            var user = GetCurrentUser();
            if (!user.IsAdmin) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var vm = new BlobConfigurationItem();
            vm.BlobType = "OneDrive";
            vm.Path = Request.Cookies["OneDrivePath"].Value;
            vm.CurrentPath = Request.Cookies["OneDriveCurrentPath"].Value;
            vm.StorageFolderPath = Request.Cookies["OneDriveFolder"].Value;
            var oneDriveClient = (OneDriveRestAPI.Client)Session["OneDriveClient"];

            var accessToken = await oneDriveClient.GetAccessTokenAsync(code);
            vm.AccessToken = accessToken.Access_Token;
            vm.RefreshToken = accessToken.Refresh_Token;

            var account = CloudAccount.Load(SystemSettings.Instance.BlobStorage, Request.Cookies["OneDriveDomain"].Value);
            var data = BlobContainerConfig.LoadFromString(account.Config);
            var configVM = new BlobConfigurationVM(data);
            RegisterItem(vm, configVM, account);

            var routeParameter = new RouteValueDictionary();
            routeParameter["domain"] = account.AccountName;
            if (account.AccountType == "SharePoint")
            {
                routeParameter["SPHostUrl"] = account.SpHostUrl;
                routeParameter["config"] = "1";
            }

            return RedirectToAction("Configuration", routeParameter);
        }

        public virtual async Task<ActionResult> ConfigurationEdit(string submitButton, BlobConfigurationItem vm)
        {
            var user = GetCurrentUser();
            if (!user.IsAdmin) throw new BlobException(BlobExceptionCode.NotAuthorization);

            //追加モード
            if (this.Request.HttpMethod == "GET" && vm.Command == "New")
            {
                return View(vm);
            }

            var blobTypeItem = vm.BlobType;
            var data = BlobService.GetCurrentBlobContainerConfig();
            var configVM = new BlobConfigurationVM(data);

            var queryParameter = new RouteValueDictionary();
            var account = BlobService.GetCurrentCloudAccount();
            if (account.AccountType == "SharePoint")
            {
                queryParameter["SPHostUrl"] = account.SpHostUrl;
                queryParameter["config"] = "1";
            }

            //Callback登録モード
            if (vm.Command == "Register")
            {
                //GoogleDrive登録モード Callback
                if (this.Request.HttpMethod == "GET" && vm.BlobType == "GoogleDrive")
                {
                    return RegisterGoogleDriveAccount(vm, configVM, queryParameter);
                }

                if (this.Request.HttpMethod == "GET" && vm.BlobType == "DropBox")
                {
                    return RegisterDropBoxAccount(vm, configVM, queryParameter);
                }
                throw new NotSupportedException();
            }

            //削除モード
            if (submitButton == ViewMessages.CmdRemove)
            {
                var current = configVM.Items.FirstOrDefault(x => x.Path == vm.CurrentPath);
                if (current == null)
                {
                    this.ModelState.AddModelError("", ViewMessages.MsgUpdateOtherUser);
                    return View(vm);
                }
                configVM.Items.Remove(current);
                var config = configVM.ToBlobContainerConfig();
                account.Config = config != null ? config.ToXml() : null;
                account.Save(SystemSettings.Instance.BlobStorage);

                //TODO：メタデータの掃除

                return RedirectToAction("Configuration", queryParameter);
            }

            //変更モード
            if (this.Request.HttpMethod == "GET" && vm.Command != "Register")
            {
                vm = configVM.Items.Where(x => x.Path == vm.Path).FirstOrDefault();
                vm.CurrentPath = vm.Path;
                if (vm == null) return RedirectToAction("Configuration", queryParameter);
                return View(vm);
            }

            //登録
            if (this.Request.HttpMethod == "POST")
            {
                if (string.IsNullOrEmpty(vm.Path))
                {
                    this.ModelState.AddModelError("Path", ViewMessages.MsgRequired);
                }
                var svc = CreateBlobService();
                if (vm.CurrentPath != vm.Path && await svc.ExistsAsync(vm.Path))
                {
                    this.ModelState.AddModelError("Path", ViewMessages.MsgAlreadyExists);
                }

                if (this.ModelState.IsValid)
                {
                    //DROPBOX
                    if (vm.BlobType == "DropBox")
                    {
                        return EntryDropBox(vm, queryParameter);
                    }

                    //GoogleDrive登録モード
                    if (vm.BlobType == "GoogleDrive")
                    {
                        return EntryGoogleDriveAccount(vm, configVM, queryParameter);
                    }

                    //ONEDRIVE認証モード
                    if (vm.BlobType == "OneDrive")
                    {
                        return EntryOneDrive(vm, queryParameter);
                    }

                    //その他
                    if (RegisterItem(vm, configVM, BlobService.GetCurrentCloudAccount()))
                    {
                        return RedirectToAction("Configuration", queryParameter);
                    }
                    else
                    {
                        return View(vm);
                    }

                }
            }
            return View(vm);
        }


        [ExcludeFromCodeCoverage]
        private ActionResult EntryDropBox(BlobConfigurationItem vm, RouteValueDictionary queryParameter)
        {
            var dropBoxClient = new DropNetClient(SystemSettings.Instance.DropBoxAppKey, SystemSettings.Instance.DropBoxAppSecret);
            var userToken = dropBoxClient.GetToken();
            queryParameter["AccessToken"] = userToken.Token;
            queryParameter["AccessSecret"] = userToken.Secret;
            queryParameter["Command"] = "Register";
            queryParameter["BlobType"] = "DropBox";
            queryParameter["Path"] = vm.Path;
            queryParameter["CurrentPath"] = vm.CurrentPath;
            queryParameter["StorageFolderPath"] = vm.StorageFolderPath;

            var url = dropBoxClient.BuildAuthorizeUrl(Url.Action("ConfigurationEdit", null, queryParameter, Request.Url.Scheme));
            return Redirect(url);
        }

        private ActionResult EntryOneDrive(BlobConfigurationItem vm, RouteValueDictionary queryParameter)
        {
            Response.Cookies["OneDriveDomain"].Value = BlobService.GetCurrentDomain();
            Response.Cookies["OneDrivePath"].Value = vm.Path;
            Response.Cookies["OneDriveCurrentPath"].Value = vm.CurrentPath;
            Response.Cookies["OneDriveFolder"].Value = vm.StorageFolderPath;


            var options = new OneDriveRestAPI.Options
            {
                ClientId = SystemSettings.Instance.OneDriveClientId,
                ClientSecret = SystemSettings.Instance.OneDriveClientSecret,
                CallbackUrl = Url.Action("ConfigurationOneDriveCallback", null, queryParameter, Request.Url.Scheme),
                AutoRefreshTokens = true,
                PrettyJson = false,
                ReadRequestsPerSecond = 2,
                WriteRequestsPerSecond = 2,

            };
            var oneDriveClient = new OneDriveRestAPI.Client(options);

            Session["OneDriveClient"] = oneDriveClient;
            // Get the OAuth Request Url
            var authRequestUrl = oneDriveClient.GetAuthorizationRequestUrl(new[] { OneDriveRestAPI.Model.Scope.Basic, OneDriveRestAPI.Model.Scope.Signin, OneDriveRestAPI.Model.Scope.SkyDrive, OneDriveRestAPI.Model.Scope.SkyDriveUpdate, OneDriveRestAPI.Model.Scope.OfflineAccess });
            return Redirect(authRequestUrl);
        }

        [ExcludeFromCodeCoverage]
        private ActionResult RegisterDropBoxAccount(BlobConfigurationItem vm, BlobConfigurationVM configVM, RouteValueDictionary queryParameter)
        {
            var dropBoxClient = new DropNetClient(SystemSettings.Instance.DropBoxAppKey, SystemSettings.Instance.DropBoxAppSecret, vm.AccessToken, vm.AccessSecret);
            var accessToken = dropBoxClient.GetAccessToken();
            vm.AccessToken = accessToken.Token;
            vm.AccessSecret = accessToken.Secret;
            vm.StorageFolderPath = string.IsNullOrEmpty(vm.StorageFolderPath) ? "/" : vm.StorageFolderPath;
            if (RegisterItem(vm, configVM, BlobService.GetCurrentCloudAccount()))
            {
                return RedirectToAction("Configuration", queryParameter);
            }
            else
            {
                return View(vm);
            }
        }

        [ExcludeFromCodeCoverage]
        private ActionResult RegisterGoogleDriveAccount(BlobConfigurationItem vm, BlobConfigurationVM configVM, RouteValueDictionary queryParameter)
        {
            string accessToken;
            string refreshToken;
            string redirectUri;
            bool hasCredential = RegisterGoogleDriveAccount(vm, out accessToken, out refreshToken, out redirectUri);

            if (hasCredential)
            {
                vm.AccessToken = accessToken;
                vm.RefreshToken = refreshToken;
                vm.StorageFolderPath = string.IsNullOrEmpty(vm.StorageFolderPath) ? "/" : vm.StorageFolderPath;

                if (RegisterItem(vm, configVM, BlobService.GetCurrentCloudAccount()))
                {
                    return RedirectToAction("Configuration", queryParameter);
                }
                else
                {
                    return View(vm);
                }
            }
            else
            {
                return View(vm);
            }
        }

        [ExcludeFromCodeCoverage]
        private ActionResult EntryGoogleDriveAccount(BlobConfigurationItem vm, BlobConfigurationVM configVM, RouteValueDictionary queryParameter)
        {
            Response.Cookies["GoogleDrivePath"].Value = vm.Path;

            vm.Command = "Register";

            string accessToken;
            string refreshToken;
            string redirectUri;
            bool hasCredential = RegisterGoogleDriveAccount(vm, out accessToken, out refreshToken, out redirectUri);

            if (hasCredential)
            {
                vm.AccessToken = accessToken;
                vm.RefreshToken = refreshToken;
                vm.StorageFolderPath = string.IsNullOrEmpty(vm.StorageFolderPath) ? "/" : vm.StorageFolderPath;

                if (RegisterItem(vm, configVM, BlobService.GetCurrentCloudAccount()))
                {
                    return RedirectToAction("Configuration", queryParameter);
                }
                else
                {
                    return View(vm);
                }
            }
            else
            {
                return new RedirectResult(redirectUri);
            }
        }

        [ExcludeFromCodeCoverage]
        private bool RegisterGoogleDriveAccount(BlobConfigurationItem vm, out string accessToken, out string refreshToken, out string redirectUri)
        {
            accessToken = refreshToken = redirectUri = "";
            Dictionary<string, object> result = GoogleDriveUtil.AuthorizeAsync(vm.Path,
                    Url.Action("IndexAsync", "GoogleAuthCallback", null, Request.Url.Scheme),
                    Url.Action("ConfigurationEdit", null, vm, Request.Url.Scheme),
                    this.HttpContext).Result;
            if (!result.Keys.Contains("RefreshToken"))
            {
                redirectUri = (string)result["RedirectUri"];
                return false;
            }
            else
            {
                accessToken = (string)result["AccessToken"];
                refreshToken = (string)result["RefreshToken"];
                return true;
            }
        }
        private bool RegisterItem(BlobConfigurationItem vm, BlobConfigurationVM configVM, CloudAccount account)
        {
            vm.Path = "/" + vm.Path.Trim('/') + "/";
            int pos = -1;

            if (!string.IsNullOrEmpty(vm.CurrentPath))
            {
                var current = configVM.Items.FirstOrDefault(x => x.Path == vm.CurrentPath);
                if (current == null)
                {
                    this.ModelState.AddModelError("", ViewMessages.MsgUpdateOtherUser);
                }
                else
                {
                    pos = configVM.Items.IndexOf(current);
                    configVM.Items.Remove(current);
                }
            }
            if (configVM.Items.Any(x => x.Path == vm.Path))
            {
                this.ModelState.AddModelError("Path", string.Format(ViewMessages.MsgExistsPath, vm.Path));
            }
            if (this.ModelState.IsValid)
            {
                if (pos >= 0)
                {
                    configVM.Items.Insert(pos, vm);
                }
                else
                {
                    configVM.Items.Add(vm);
                }
                var config = configVM.ToBlobContainerConfig();
                try
                {
                    if (config != null)
                    {
                        config.CreateBlobContainer(SystemSettings.Instance.BlobContainerName, SystemSettings.Instance.BlobStorage).Dispose();
                    }
                    account.Config = config != null ? config.ToXml() : null;
                    account.Save(SystemSettings.Instance.BlobStorage);
                    return true;
                }
                catch (Exception ex)
                {
                    this.ModelState.AddModelError("", ex);
                }
            }
            return false;
        }



        public ActionResult DropBoxAuth(string appKey, string appSecret, string dropboxFolder, string folderPath)
        {
            var dropBoxClient = new DropNetClient(appKey, appSecret);
            var userToken = dropBoxClient.GetToken();
            Response.Cookies["DropBoxPath"].Value = folderPath;
            Response.Cookies["DropBoxAppKey"].Value = appKey;
            Response.Cookies["DropBoxAppSecret"].Value = appSecret;
            Response.Cookies["DropBoxFolder"].Value = dropboxFolder;
            Response.Cookies["DropBoxToken"].Value = userToken.Token;
            Response.Cookies["DropBoxSecret"].Value = userToken.Secret;
            var url = dropBoxClient.BuildAuthorizeUrl(Url.Action("Configuration", null, new { callback = true }, Request.Url.Scheme));
            return Redirect(url);
        }

        public ActionResult CloudSettings()
        {
            var user = GetCurrentUser();
            if (!user.IsAdmin) throw new BlobException(BlobExceptionCode.NotAuthorization);

            var data = BlobService.GetCurrentBlobContainerConfig();
            var vm = new CloudSettingsVM(BlobService.GetCurrentCloudAccount());
            return View(vm);
        }

        [HttpPost]
        public ActionResult CloudSettings(string submitButton, CloudSettingsVM vm)
        {
            var user = GetCurrentUser();
            if (!user.IsAdmin) throw new BlobException(BlobExceptionCode.NotAuthorization);
            var account = BlobService.GetCurrentCloudAccount();
            if (account.IsLocked)
            {
                throw new BlobException(BlobExceptionCode.CloudAccountLocked);
            }
            account.AllowIPAddresses = vm.AllowIPAddresses;
            if (!account.CanAccessIPAddress(WebUtil.GetIPAddress()))
            {
                this.ModelState.AddModelError("AllowIPAddresses", string.Format(ViewMessages.MsgCurrentIPAddressDoseNotAllow, WebUtil.GetIPAddress()));
            }

            if (this.ModelState.IsValid)
            {
                account.Administrators = vm.Administrators;
                account.AzureAdMode = vm.AzureAdMode;
                account.Authority = vm.Authority;
                account.CloudStorageLightClientId = vm.CloudStorageLightClientId;
                account.CloudStorageLightAppKey = vm.CloudStorageLightAppKey;
                account.TenantId = vm.TenantId;
                account.RmsServicePrincipal = vm.RmsServicePrincipal;
                account.RmsSymmetricKey = vm.RmsSymmetricKey;
                account.RmsExtranetUrl = vm.RmsExtranetUrl;
                account.RmsIntranetUrl = vm.RmsIntranetUrl;
                account.AcceptMailAddressPattern = vm.AcceptMailAddressPattern;
                account.GuestMailAddressPattern = vm.GuestMailAddressPattern;
                account.ClientDownloadPage = vm.ClientDownloadPage;
                account.CanFullTextSearch = vm.CanFullTextSearch;
                account.CanCreateSelfAccount = vm.CanCreateSelfAccount;
                account.CanUseBackupFunction = vm.CanUseBackupFunction;
                account.CanUseAutoPassword = vm.CanUseAutoPassword;
                account.CanUseUserPasswordProtect = vm.CanUseUserPasswordProtect;
                account.CanUseRMS = vm.CanUseRMS;
                account.CanUseGuestAccount = vm.CanUseGuestAccount;
                account.AllowIPAddresses = vm.AllowIPAddresses;
                account.UseClientTokenForIPCheck = vm.UseClientTokenForIPCheck;
                account.CanUseTransferFile = vm.CanUseTransferFile;
                account.AuthTicketTimeout = vm.AuthTicketTimeout;
                account.LogRetentionDays = vm.LogRetentionDays;
                account.AuthFtpServer = vm.AuthFtpServer;
                account.AuthFtpDomain = vm.AuthFtpDomain;
                account.Save(SystemSettings.Instance.BlobStorage);
            }
            return View(vm);

        }

        public async Task<ActionResult> LogView(DateTime? fromDate, DateTime? toDate,string path, string userName, string actionName, int size = 500)
        {
            BlobUser user = GetCurrentUser();
            if (!user.IsAdmin) throw new BlobException(BlobExceptionCode.NotAuthorization);
            var logs = new List<AccessLog>();
            using (var svc = CreateBlobService())
            {
                if (!fromDate.HasValue)
                {
                    if (toDate.HasValue)
                    {
                        fromDate = toDate.Value.AddDays(-20);
                    }
                    else
                    {
                        fromDate = DateTime.UtcNow.Date.AddDays(-20);
                    }
                }

                logs = await svc.GetLog(fromDate, toDate, userName, string.IsNullOrEmpty(actionName) ? null : actionName.Split(','), path, size);
                return View(logs.Where(x => x.AccountName == BlobService.GetCurrentDomain()).OrderByDescending(x => x.Date).Take(size));
            }
        }

        public ActionResult CreateBlobSas(int timespan=1, bool write=false)
        {

            using (var svc = CreateBlobService())
            {
                var user = GetCurrentUser();
                return Content(svc.CreateBlobSas(user, timespan, write));
            }
        }

    }
}
